摘要

这期我复习多态,有很多我想知道的关于多态的话题。有了继承组合多态之后,编程已经变得很有趣。

什么是多态

简单说,多态就是指一个实例,可以被他本身的数据类型引用,也可以赋值给他的基类。书里的原话:

“an object can be used as its own type or as an object of its base type”

典型的例子:

//基类:动物
class Animal {
	public String bark(){return "**";}
}
//子类:小狗汪汪叫
class dog extends Animal {
	public String bark(){return "WongWong";}
}
//子类:小猫喵喵叫
class cat extends Animal {
	public String bark(){return "MiuMiu";}
}

比如,小猫,小狗是动物的两个子类。我在创建小狗,小猫实例的时候,可以把他们统一赋值给他们的基类:Animal类型。

//某天晚上宠物叫
class OneNight {
	public void petBark(){
		//两只宠物
		Animal myPet1=new dog();
		Animal myPet2=new cat();
		//一起叫
		System.out.println(myPet1.bark());
		System.out.println(myPet2.bark());
	}
}

输出的结果就是小猫小狗叫WongWong,MiuMiu,而不会调用基类Animal.bark()。

多态背后的原因:后期绑定

多态特性归功于后期绑定。显然,之前的例子里,myPet1.bark(),如果在OneNight类编译的时候就绑定的话,就会绑定成Animal.bark()。因为明显myPet1和myPet2指向的都是Animal类型。

而后期绑定,是把方法绑定的动作推迟到了程序运行的runtime。等new dog(),new cat()真正实例化完成以后,直接根据实例的类型来决定绑定哪个bark()方法。

后期绑定在编译层面的细节

《多态在 Java 和 C++ 编程语言中的实现比较》这篇文章详细介绍了JVM是如何实现后期绑定的。我只是对其中的核心内容做简单的转述,方便自己今后查阅。

文章里使用了一个男孩儿,女孩儿的栗子,Person继承自Object类,然后再异化成Boy和Girl。

//基类
 class Person {
 public String toString(){
    return "I'm a person.";
	 }
 public void eat(){}
 public void speak(){}

 }
//子类男孩儿
 class Boy extends Person{
 public String toString(){
    return "I'm a boy";
	 }
 public void speak(){}
 public void fight(){}
 }
//子类女孩儿
 class Girl extends Person{
 public String toString(){
    return "I'm a girl";
	 }
 public void speak(){}
 public void sing(){}
 }

然后开个Party类,创建女孩实例,向上转型放在Person类型数据里,然后调用speak()方法。

 class Party{
 	void happyHour(){
 		Person g = new Girl();
 		g.speak();
	}
}

最后runtime程序就运行Party的happyHour()方法。

public Run {
	public static void main (String[] args) {
		Party ourParty = new Party();
		ourParty.happyHour();
	}
}

前期不绑定:编译以后的class文件

之前已经说了,所谓后期绑定,就是编译的时候不绑定调用方法的数据类型。多态出现在Party类里。编译好的Party.class文件里g.speak()是不会绑定speak()方法的数据类型的。但也不能什么都不留,实际上,在这里编译器会留下的信息是:


invokevirtual Person offset#15

翻译过来就是:等着runtime动态调用Person类Index为15的方法。

这里要先科普两个东西。第一,invokevirtual是JVM的动态调用指令之一。JVM 的方法调用指令有四个,分别是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。

第二,偏移值15。这是因为编译完的class文件中,类的每个方法都有一个编号Index。很重要的一点是,子类中继承自基类的方法的Index,都和其在基类中的Index相同。比如,toString()是Object类中的Index是100,当他被继承到Person类以后,Index还是100,在Boy和Girl里也是100不变。有了这个重要信息,JVM后期绑定时,无论runtime实例属于基类还是哪个子类,都能准确定位需要绑定的方法。

但实际上JVM编译器在编译好的class文件里,是不会显示地写出一句invokevirtual Person offset#15,而是只留下一条密码:


invokevirtual #12

因为编译器为了尽量减少数据的冗余,编译完的信息都是高度结构化的数据,其中很重要的一部分叫做常量池(constant pool)

常量池由一组数据表组成,其中保存的是一个 Java 类引用的一些常量信息,包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池,当类被载入到虚拟机内部的时候,在内存中产生类的常量池叫运行时常量池。下图展示了常量池中一些重要的表, jvm2

我们并不需要完全了解常量池的运作,可以把他当做一个黑箱,之前的那条密码中的数字#12就是指常量池中索引为12的条目。通过查找这个黑箱,我们就可以翻译为invokevirtual Person offset#15这条重要信息。

后期绑定的过程

多态向上转型前期跳过绑定时留在class文件里的信息我们已经知道了。现在就来看看后期runtime怎么通过这组信息动态绑定到需要调用的数据类型。

JVM内存回顾:方法区

讲之前,先回顾一下之前《Java垃圾回收初探》里JVM对内存的分配。 jvm2 简单讲,就是三大块,

原先因为讲垃圾回收,主角当然是逻辑堆。现在讲后期绑定,就轮到方法区做主角了。

加载类文件,生成”方法表”

runtime的时候,根据我们的主线程Run类的main()方法,就需要加载Party类,Person类,Girl类等所有的相关类。

首先,JVM有一个叫类加载器(Classloader)的东西,当程序的某一线程运行需要某一个类的时候,这个加载器就会这个类的class文件,产生这个类的方法代码、类变量、成员变量的定义,然后都存放在上面讲的方法区里。

接下来,刚才加载的数据里有很重要的一个,叫方法表的东西。比如,Person,Boy,Girl,Object几个类加载到方法区后就各自产生一个方法表, methodArea

JVM当然不会傻傻地在方法区列出所有类的所有方法,这个方法表的作用就是消除数据冗余。所有方法代码都只存一份,其他都用方法表列出引用。比如Girl 和 Boy 的方法表包含所有方法的引用:

动态绑定

dynBanding 如上图所示,主线程运行Party类happyHour()方法里的g.speak()的时候,就读到了前期编译的时候留下的信息:invokevirtual Person offset#15

这时候g的实例已经产生,动态调用命令invokevirtual的意思就是等g实例产生以后,找到逻辑堆里g对象实际对应的Girl类,再根据偏移量15找到是Girl.speak()方法。这时候,方法类型绑定完成。

final关键字的真正含义

与其说是禁止用户修改,不如说是禁止后期绑定,强制在编译class文件时就前期绑定好。

static修饰符,隐含强制final。

多态对”字段”无效

我们一直是以一种歌功颂德的心态在描述和看待后期绑定和多态的!但事实上多态远非完美!最大的问题在于多态只对”方法”有效,对”字段”无效。子类向基类转型后,直接访问字段,返回值是基类的字段值。

下面的这个例子,子类独轮车,继承自基类轮子车。他们都只有一个表示自身种类的字段,和用来显示字段的方法。

显然独轮车重新定义了info字段,改成他自己的名字。

//基类:轮子车
class Cycle {
    //field
    public String info="I am a cycle!";
    //method to show the field
    public void getInfo(){
        System.out.println(this.info);
    }

}

//子类:独轮车
class UniCycle {
	//field: 企图覆盖基类的info字段
    public String info="I am a Unicycle!";
}    

但实际上,通过直接访问字段,我们发现向上转型后,输出的仍然是基类轮子车的info。我们期待的多态并没有发生。

//创建独轮车实例,然后向上转型
Cycle cycle1=new UniCycle();
//直接访问字段
System.out.println(cycle1.info);	//output: I am a cycle!

如果想要访问独轮车的info字段,必须调用显示info字段的getInfo()方法。

//通过方法访问字段
cycle1.getInfo();	//output: I am a Unicycle!

问题到底在哪儿呢?

先重提类的初始化顺序

问题的原因要追溯到类的初始化过程,前几章已经好几次提过类的初始化顺序,这里为了说清楚这个问题,还是再重提一下。很清楚的是类初始化的过程中都会调用构造函数,无论我们有没有显式的定义构造函数。继承类,在构造自身之前会先构造一个父类,然后再把父类”重写”成自己。所以,子类构造过程要经过下面三个阶段:

子类字段不具备多态性的原因

关键问题出在子类调用完基类构造器,创建完父类实例之后,开始要初始化自己成员字段时,如果子类拥有一个和父类的同名字段时,编译器并不会像对待方法那样重写字段,而是同时保有两个同名字段,一个叫this.field,一个叫super.field。对象在方法内调用自身字段,可以显式写明要哪一个,如果不写,也是优先调用this.field。但当向上转型的对象在外部被调用时,默认返回值就是super.field了,因为当前对象本身就是一个基类的对象。

这就造成了多态的小小”失态”:只有成员方法具有多态性,而成员字段不具备多态性。 而且,这样的错误编译器是不报错的,很隐蔽。

我们当然可以把子类字段设置成和基类不同的名字,但这样已经失去了多态去耦合的意义。

所以这里又再次证明了把字段设成private这种风格的重要性。关掉所有字段的外部访问权限,而一律使用getField()和setField()代理方法来访问,这样就不会出问题,可以愉快地享受多态带来的好处了。当然,最好也养成习惯,不要给基类和子类设置同名字段,不要老想着会重写,记住子类字段是不会覆盖基类字段的。

另外,private和static修饰的内容,不管是字段还是方法,都是不具备多态性的,编译的时候就绑定了。这个比较好理解。

关于”covatiant”协变

书里提到一种子类里某一方法的返回值,恰巧是基类里同一方法返回值的子类,这种特殊情况。书里给的例子很清楚,

class Grain {
	public String toString() { return "Grain"; }
}
class Wheat extends Grain {
	public String toString() { return "Wheat"; }
}
class Mill {
	Grain process() { return new Grain(); }
}
class WheatMill extends Mill {
	Wheat process() { return new Wheat(); }
}

情况是这样,WheatMill类继承自Mill基类。问题在于,WheatMill在重写Mill的process()方法的时候,把返回值给改了。原本返回Grain类,现在返回Grain的子类Wheat了。

从程序员的角度,小麦磨坊当然只磨小麦啦,这是合理的。但从编译的角度,返回值不同,就是两个不同的方法,不会被覆盖的。最简单,像下面两个sayHello()函数,一个有返回值,一个没有,在Hello类里,肯定是两个不同的方法。

//无返回值
public void sayHello(){
	System.out.println("Hello!");
}
//返回String
public String sayHello(){return "Hello";}

根据之前的初始化的顺序,在创建子类WheatMill的时候,先会创建一个基类GrainMill类,里面包含一个Grain process(){}方法。当后来创建子类WheatMill的Wheat process(){}方法的时候,不覆盖Grain process(){}。导致WheatMill()实际含有两个不同的process()方法。

平时用的时候不会有问题,

//小麦磨坊
m = new WheatMill();
//麦子
w = new Wheat();
//小麦磨坊磨麦子
w = m.process();

但程序员想用到多态的时候,就出问题了,g = m.process()这一行,明显调用的是基类Grain里的Grain process(){}方法。

//还是小麦磨坊
m = new WheatMill();
//这次要磨别的谷子比如大米了
Grain g = new Grain();
//磨出来的会是什么呢?
g = m.process();
	System.out.println(g);
}

所以,Java5以前,这段代码磨出的是大米:

Grain

但Java5以后的版本,磨出的还是麦子:

Wheat

因为编译器当看到子类同一方法返回值是基类返回值的子类时,就自动协同转型,认为是同一个方法,子类Wheat process(){}就把基类Grain process(){}覆盖掉了。

这个特性我并不很喜欢,因为虽然用可以用多态了,但实际上破坏了编译的”程序正义”,有点法外开恩的意思。

后期绑定的小缺点

后期绑定确实很美妙,但实现起来也有他的问题。比较典型的一个例子如下:

class Shape{
	//member method
	public void draw(System.out.println("Which shape?"));

	//constructor
	Shape(){
		draw();
	}
}

class Circle extends Shape{
	//member method
	public void draw(System.out.println("Turn a round!"));

	Circle(){
		draw();
	}
}

public class DrawShape{
	//main method
	public static void main(String[] args){
		Shape newShape=new Circle();
	}
}

为了初始化newShape实例,Circle的构造器先调用基类Shape的构造器,构造器内部调用了成员方法draw(),但问题在于这时候draw()方法显然还没有绑定,因为根据动态绑定原则,光看Shape.draw()这句话是无法知道到底调用Shape.draw()还是Circle.draw()的,一切都要等newShape实例初始化完成,根据他的类型再做定论,但显然这一刻永远等不到,newShape这不正在初始化呢嘛。所以问题就来了,卡住了。

为了解决这种互相依赖锁死的问题,编译器强行规定这时候就不动态绑定,不多态了,直接调用基类Shape的draw()。所以最后的输出会是:

Which shape?
Turn a round!

这种情况容易产生很隐蔽的bug。所以这种构造函数内调用成员方法的做法不稳妥,Bruce Eckel劝我们尽量少用。最多调用private和final方法。

从多态的角度比较继承和组合

前几章作者一直更倾向于使用组合。从多态的角度,作者还是坚持尽量多用组合。谨慎使用继承,除非在必要的时候。但多态没有继承,根本玩不起来。所以,一个合理的风格应该是:

“Use inheritance to express differences in behavior, and fields to express variations in state.”

这个风格我是有体会的,当一个东西有很多种变体的时候,用基类描述通用行为和参数,然后在不同的子类里描述各自的特殊行为参数,这样用继承是合理的。而且在实例化的时候,有很强的需要用到多态,向上转型,面向通用基类编程。

之前《氏族战争》里,作为一个生产Human的工厂类Clan,我就设计成一个虚拟工厂基类,来描述构造一个人类的通用参数和行为。但下面,每个种族就有自己特殊的技能和遗传特性了,这就是作者原文提到的”参数和行为的差异性”了。”兽族”,”矮人族”,”鼠族”,继承Human的共同属性,然后再各自异化,就显得比较合理。

最后游戏的应用层面,运用多态完全就是面向Human这个虚拟接口编程了,底层哪怕再扩展100个种族,游戏应用层面完全不需要改代码。

State Pattern

为了证明组合比继承更灵活,书里介绍了一个State Pattern。用的是演员的例子。State Pattern的好处,就是可以在runtime随意切换实例的类型。

演员分两类状态,开心的,和不开心的。

//演员基类
class Actor {
	public void act() {}
}
//开心演员
class HappyActor extends Actor {
	public void act() { print("HappyActor");
}
//难过演员
class SadActor extends Actor {
	public void act() { print("SadActor"); }
}

舞台不是继承演员,而是组合,内部包含一个演员,

class Stage {
	private Actor actor = new HappyActor();
	public void change() { actor = new SadActor(); }
	public void performPlay() { actor.act(); }
}

关键是里面的change()方法,可以临时切换舞台上演员的状态,一会儿是开心演员,一会儿是不开心演员。所谓的灵活就灵活在这儿。

继承是纯替换还是扩展?

纯替换,就是指继承的时候,老老实实只重写基类方法,而不添加新方法。 substitution 但大多数情况,还是会在子类里加一点新方法,就是扩展了。 extention

作者好像不鼓励扩展。因为,这样做在向下转型的时候不安全。基类强制转换成子类以后,并没有能力执行子类里的扩展方法。Java当然会检查数据类型,错误情况下会抛出ClassCastException异常。

但从设计角度,让兽人偶尔做做正常人类不做的事,比如说吃人,好像也无可厚非。不然也没有一种可以替代的模式,让兽人完成吃人这个动作。

练习题

Exercise 9

Create an inheritance hierarchy of Rodent: Mouse, Gerbil, Hamster, etc. In the base class, provide methods that are common to all Rodents, and override these in the derived classes to perform different behaviors depending on the specific type of Rodent. Create an array of Rodent, fill it with different specific types of Rodents, and call your base-class methods to see what happens.

基类,老鼠:Rodent.java

鼠类的基类。

package com.ciaoshen.thinkinjava.chapter8; import java.util.*;

class Rodent {

/**
 *  PUBLIC PROXY OF CONSTRUCTOR
 */
public static Rodent newRodent(){
    return new Rodent();
}

/**
*  PUBLIC METHODS
*/
//each rodent will eat one food and flee
public void invade(HaveFood granary) {
    String[] foodList=granary.haveFood();
    if(foodList!=null){
        for(int i=0;i<foodList.length;i++){
            if(foodList[i]==this.food){
                foodList[i]=eat(foodList[i]);
                System.out.println(foodList[i]);
                break;
            }
        }
    }
}

//chew some part of the food
public String eat(String food){
    return food+" eaten by "+this.name;
}
//Although Java pass the arguments by their references, this method can not change the value of "food"
//Because 《food=food+"x"》 will create a new copy of string food, thus will not change the original argument
public void eat2(String food){
    food=food+" eaten by "+this.name;
}

public String toString(){
    return "[Name: "+this.name+"], [Food: "+this.food+"]";
}

/**
 *  PACKAGE ACCESS CONSTRUCTOR
 */
Rodent(){
    this.name="";
    this.food="";
}

/**
 *  PACKAGE ACCESS FIELDS
 */
String food;
String name;

/**
 *  MAIN
 *  @param args void
 */
public static void main(String[] args){
    System.out.println(Rodent.newRodent().toString());
}

}


##### 三种子类:家鼠,沙鼠,仓鼠
三种不同的鼠类:家鼠,沙鼠,仓鼠,继承鼠类的共同属性和行为。各自的特点是爱吃的东西不同:
* **家鼠**爱吃大米
* **沙鼠**爱吃麦子
* **仓鼠**爱吃坚果。


**Mouse.java**
```java
/**
 *  Exercise 9 - Polymophism
 *  Mouse is a type of Rodent.
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

class Mouse extends Rodent {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Rodent newRodent(){
        return new Mouse();
    }

    /**
     *  PUBLIC METHODS
     */

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Mouse(){
        super.name="mouse";
        super.food="rice";
    }
    /**
     *  PRIVATE FIELDS
     */

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        System.out.println(Mouse.newRodent().toString());
    }

}

Gerbil.java

/**
 *  Exercise 9 - Polymophism
 *  Gerbil is a type of Rodent.
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

class Gerbil extends Rodent {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Rodent newRodent(){
        return new Gerbil();
    }

    /**
     *  PUBLIC METHODS
     */

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Gerbil(){
        super.name="gerbil";
        super.food="wheat";
    }
    /**
     *  PRIVATE FIELDS
     */

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        System.out.println(Gerbil.newRodent().toString());
    }

}

Hamster.java

/**
 *  Exercise 9 - Polymophism
 *  Humster is a type of Rodent.
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

class Hamster extends Rodent {

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public static Rodent newRodent(){
        return new Hamster();
    }

    /**
     *  PUBLIC METHODS
     */

    /**
     *  PRIVATE CONSTRUCTOR
     */
    private Hamster(){
        super.name="hamster";
        super.food="nut";
    }
    /**
     *  PACKAGE ACCESS FIELDS
     */

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        System.out.println(Hamster.newRodent().toString());
    }

}
一类接口:有食物的地方

有食物的地方只是一种接口,指粮仓,厨房,垃圾箱这类鼠类主要的食物来源地。

HaveFood.java

/**
 *  Exercise 9 - Polymophism
 *  HaveFood is an interface for the place where we can find food.
 *  Ex: Granary, Kichen, TrashCan
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

interface HaveFood {

    /**
     *  PUBLIC METHODS
     */
    public String[] haveFood(); //return the food list
    public String randomFood(); //randomly get a type of food
    public String checkFood();
    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    public void storeFood(); //charge the food

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */

    /**
     *  PACKAGE ACCESS FIELDS
     */
    String[] foodList=null;

    /**
     *  MAIN
     *  @param args void
     */

}
有食物的地方:粮仓

粮仓实现了有食物的地方这一接口。粮仓里可能随机储藏的食物包括:玉米,大米,小麦,面粉,牛奶,坚果,鸡蛋,糖,油。

Granary.java

/**
 *  Exercise 9 - Polymophism
 *  Granary implements the HaveFood interface
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

class Granary implements HaveFood{

    /**
     *  PUBLIC METHODS
     */
    public String[] haveFood(){
        return this.foodList;
    }

    public String randomFood(){
        Random foodRander=new Random();
        int foodId=foodRander.nextInt(9);
        String food="";
        switch(foodId){
            case 0:
                food="corn"; break;
            case 1:
                food="wheat"; break;
            case 2:
                food="rice"; break;
            case 3:
                food="flour"; break;
            case 4:
                food="milk"; break;
            case 5:
                food="nut"; break;
            case 6:
                food="egg"; break;
            case 7:
                food="suger"; break;
            case 8:
                food="oil"; break;
        }
        return food;
    }

    public String checkFood(){
        String content="";
        for(int i=0;i<this.foodList.length;i++){
            content=content+"["+this.foodList[i]+"]";
            if(i!=this.foodList.length-1){
                content+=", ";
            }
        }
        return content;
    }

    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */
    //randomly charge the food
    public void storeFood(){
        for(int i=0;i<this.foodList.length;i++){
            this.foodList[i]=randomFood();
        }
    }

    /**
     *  PACKAGE ACCESS CONSTRUCTOR
     */
    Granary(int size){
        this.foodList=new String[size];
    }

    /**
     *  PACKAGE ACCESS FIELDS
     */
    String[] foodList=null;

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        Granary testGranary=new Granary(10);
        testGranary.storeFood();
        System.out.println(testGranary.checkFood());
    }

}
剧本: 老鼠总动员

剧本很简单,一波10只随机产生的老鼠军团,入侵粮仓。

RodentStory.java

/**
 *  Exercise 9 - Polymophism
 *  A large of rodents invade a granary.
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */
package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;

public class RodentStory {
    /**
     *  PUBLIC PROXY OF CONSTRUCTOR
     */

    /**
     *  PUBLIC METHODS
     */
    public static void rodentInvadeGranary(){
        //create a granary
        Granary myGranary=new Granary(50);
        myGranary.storeFood();
        //check the granary
        System.out.println(myGranary.checkFood());
        //create a group of rodents
        Rodent[] rodentLegion=groupOfRodent(10);
        //rodents invade the granary
        for(Rodent r: rodentLegion){
            r.invade(myGranary);
        }
        //check the granary
        System.out.println("=================================");
        System.out.println(myGranary.checkFood());
    }

    public static Rodent[] groupOfRodent(int number){
        Rodent[] rodentLegion=new Rodent[number];
        Random rodentRander=new Random();
        for(int i=0;i<number;i++){
            int rodentType=rodentRander.nextInt(3);
            switch(rodentType){
                case 0:
                    rodentLegion[i]=Mouse.newRodent(); break;
                case 1:
                    rodentLegion[i]=Gerbil.newRodent(); break;
                case 2:
                    rodentLegion[i]=Hamster.newRodent(); break;
            }
        }
        return rodentLegion;
    }
    /**
     *  PRIVATE CONSTRUCTOR
     */

    /**
     *  PRIVATE FIELDS
     */

    /**
     *  MAIN
     *  @param args void
     */
    public static void main(String[] args){
        RodentStory.rodentInvadeGranary();
    }

}
结果

老鼠军团入侵之前,粮仓一片安详。

[oil], [rice], [milk], [nut], [wheat], [oil], [nut], [milk], [rice], [nut], [nut], [rice], [wheat], [wheat], [wheat], [corn], [corn], [corn], [oil], [wheat], [flour], [egg], [milk], [rice], [suger], [corn], [nut], [suger], [rice], [egg], [egg], [suger], [suger], [milk], [egg], [nut], [egg], [rice], [corn], [corn], [corn], [flour], [oil], [milk], [oil], [egg], [rice], [rice], [nut], [corn]

入侵之后,一片狼藉。特定种类的老鼠只吃特殊种类的食物,凶手们很快就被找到啦。

[oil], [rice eaten by mouse], [milk], [nut eaten by hamster], [wheat eaten by gerbil], [oil], [nut eaten by hamster], [milk], [rice eaten by mouse], [nut], [nut], [rice eaten by mouse], [wheat eaten by gerbil], [wheat eaten by gerbil], [wheat eaten by gerbil], [corn], [corn], [corn], [oil], [wheat], [flour], [egg], [milk], [rice eaten by mouse], [suger], [corn], [nut], [suger], [rice], [egg], [egg], [suger], [suger], [milk], [egg], [nut], [egg], [rice], [corn], [corn], [corn], [flour], [oil], [milk], [oil], [egg], [rice], [rice], [nut], [corn]
小插曲:Java参数传址,但警惕String

代码过程中出现一个有意义的小插曲:当时我想让被老鼠咬坏的食物改名字。于是有了下面的方法:

public void eat2(String food){
    food=food+" eaten by "+this.name;
}

Java是传址的,我想当然,函数内部会改变参数food的值(表示被老鼠咬过了)。但结果和我想的相反,food不会被改变。

原因参见这个帖子:StackOverFlow:为什么String当参数无法被改变?

问题就在于a=a+”x”这个操作具有欺骗性。执行之后会创建另一个新的Object,也就是a完全指向了另一个新对象的地址。

String a = "A";	//a指针指向地址x0ffff
		//地址x0ffff存着对象"A"
String b = a;	//b指针也指向地址x0ffff

b = b + "x";	//在地址x0bbbb创建对象"Ax"
		//b指针指向地址x0bbbb

Exercise 10

Create a base class with two methods. In the first method, call the second method. Inherit a class and override the second method. Create an object of the derived class, upcast it to the base type, and call the first method. Explain what happens.

基类: ExerTenBase.java
/**
 *  Exercise 10 - Polymophism
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */


package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;


class ExerTenBase {

    /**
     *  PUBLIC METHOD
     */
    public void call(){beCalled();}

    public void beCalled(){System.out.println("From Base!");}

    /**
     *  PRIVATE CONSTRUCTOR
     */

    /**
     *  PRIVATE FIELD
     */

    /**
     *  MAIN
     */
    public static void main(String[] args){}
}
子类: ExerTenInherit.java
/**
 *  Exercise 10 - Polymophism
 *  @author wei.shen@iro.umontreal.ca
 *  @version 1.0
 */

package com.ciaoshen.thinkinjava.chapter8;
import java.util.*;


class ExerTenInherit extends ExerTenBase{

    /**
     *  PUBLIC METHOD
     */
    public void beCalled(){System.out.println("From Derived!");}

    /**
     *  PRIVATE CONSTRUCTOR
     */

    /**
     *  PRIVATE FIELD
     */

    /**
     *  MAIN
     */
    public static void main(String[] args){
        ExerTenBase testObject=new ExerTenInherit();
        testObject.call();
    }
}
结果
MacBook-Pro-de-Wei:java Wei$ sh run.sh ExerTenInherit
From Derived!

结果是多态生效,执行了子类的方法。

原因就是因为多态。基类和子类拥有同一个call()函数,但有不同的beCalled()。因为子类实例创建的时候,重写了基类的beCall()方法。然后,后期绑定的时候,找到子类实例,调用了子类重写以后的beCall()方法。

Exercise 12

Modify Exercise 9 so that it demonstrates the order of initialization of the base classes and derived classes. Now add member objects to both the base and derived classes and show the order in which their initialization occurs during construction.

理论上的调用顺序:

所以我在block{}和构造器里都插入了print语句。

显示各部分构造过程:Rodent.java
	//构造器
    Rodent(){
        System.out.println("Rodent constructor{}");
        this.name="";
        this.food="";
        System.out.println(">>> A Rodent model is prepared!");
    }

    //构造器之前会运行block
    {
        System.out.println("Object Constructor{}");
        System.out.println("Before Rodent constructor{}: Initialize fields and methods.");
    }
显示各部分构造过程:Mouse.java
	//构造器
    private Mouse(){
        System.out.println("Mouse constructor{}");
        super.name="mouse";
        super.food="rice";
        System.out.println(">>> A Mouse join the rodent legion!!!");
        System.out.println("");
    }

    //构造器之前会运行block
    {
        System.out.println("Before Mouse constructor{}: Initialize fields and methods.");
    }
显示各部分构造过程:Gerbil.java
	//构造器
    private Gerbil(){
        System.out.println("Gerbil constructor{}");
        super.name="gerbil";
        super.food="wheat";
        System.out.println(">>> A Gerbil join the rodent legion!!!");
        System.out.println("");
    }

    //构造器之前会运行block
    {
        System.out.println("Before Gerbil constructor{}: Initialize fields and methods.");
    }
显示各部分构造过程:Hamster.java
	//构造器
    private Hamster(){
        System.out.println("Hamster constructor{}");
        super.name="hamster";
        super.food="nut";
        System.out.println(">>> A Hamster join the rodent legion!!!");
        System.out.println("");
    }

    //构造器之前会运行block
    {
        System.out.println("Before Hamster constructor{}: Initialize fields and methods.");
    }
结果

和理论上的顺序一致:

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Gerbil constructor{}: Initialize fields and methods.
Gerbil constructor{}
>>> A Gerbil join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Gerbil constructor{}: Initialize fields and methods.
Gerbil constructor{}
>>> A Gerbil join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Gerbil constructor{}: Initialize fields and methods.
Gerbil constructor{}
>>> A Gerbil join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Gerbil constructor{}: Initialize fields and methods.
Gerbil constructor{}
>>> A Gerbil join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Mouse constructor{}: Initialize fields and methods.
Mouse constructor{}
>>> A Mouse join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Gerbil constructor{}: Initialize fields and methods.
Gerbil constructor{}
>>> A Gerbil join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Mouse constructor{}: Initialize fields and methods.
Mouse constructor{}
>>> A Mouse join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Hamster constructor{}: Initialize fields and methods.
Hamster constructor{}
>>> A Hamster join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Mouse constructor{}: Initialize fields and methods.
Mouse constructor{}
>>> A Mouse join the rodent legion!!!

Object Constructor{}
Before Rodent constructor{}: Initialize fields and methods.
Rodent constructor{}
>>> A Rodent model is prepared!
Before Mouse constructor{}: Initialize fields and methods.
Mouse constructor{}
>>> A Mouse join the rodent legion!!!